#include "Demuxer.h"

Demuxer::Demuxer(char *url)
{
    if(url)
	   m_Url = url;
	m_AudioStream = -1;
	m_Ic = NULL;
	m_InterrputCB = NULL;
	m_CBPara = NULL;

    /*音频参数*/
    m_AudioStream = -1;
    m_AudioCodec = -1;
    m_AudioSampleRate = -1;
    m_AudioChannels = -1;
    m_AudioChannelLayout = -1;
    m_AudioFormat = -1;
    m_AudioBitRate = -1;
    m_AudioFrameSize = 0;
    m_AudioTimeBase.num = 0;
    m_AudioTimeBase.den = 1;

    /*视频参数*/
    m_VideoStream = -1;
    m_VideoCodec = -1;
    m_VideoHeight = -1;
    m_VideoWidth = -1;
    m_VideoFormat = -1;
    m_VideoBitRate = -1;
    m_VideoFPS = -1;
    m_VideoTimeBase.num = 0;
    m_VideoTimeBase.den = 1;

    m_Duration = 0;
    m_StreamBitRate = -1;
}

void Demuxer::ffmpeg_show_error(const char *msg, int err)
{
    char errbuf[128];
    const char *errbuf_ptr = errbuf;

    if (av_strerror(err, errbuf, sizeof(errbuf)) < 0)
        errbuf_ptr = strerror(AVUNERROR(err));
    LOGE("%s: %s", msg, errbuf_ptr);
}


#define FFMPEGVERSION_GET_MAJOR(x)     ((x >> 16) & 0xFF)
#define FFMPEGVERSION_GET_MINOR(x)     ((x >>  8) & 0xFF)
#define FFMPEGVERSION_GET_MICRO(x)     ((x      ) & 0xFF)

void Demuxer::ffmpeg_show_version(const char *module, unsigned version)
{
    LOGD("%-*s: %u.%u.%u",13, module,
           (unsigned int)FFMPEGVERSION_GET_MAJOR(version),
           (unsigned int)FFMPEGVERSION_GET_MINOR(version),
           (unsigned int)FFMPEGVERSION_GET_MICRO(version));
}

Demuxer::~Demuxer()
{
    close();
}

void Demuxer::close()
{
    LOGI("Demuxer close");
    if (m_Ic) {
        avformat_close_input(&m_Ic);
        m_Ic = NULL;
    }
}

void Demuxer::setUrl(char *url)
{
    if(url)
        m_Url = url;
}

int Demuxer::open()
{
	LOGD("Demuxer init url:%s", m_Url.c_str());
	LOGD("===== versions =====");
    LOGD("FFmpeg %s", av_version_info());
    ffmpeg_show_version("libavutil", avutil_version());
    ffmpeg_show_version("libavcodec", avcodec_version());
    ffmpeg_show_version("libavformat", avformat_version());
    ffmpeg_show_version("libswscale", swscale_version());
    ffmpeg_show_version("libswresample", swresample_version());
	
	int ret = 0, err = 0;
    m_Ic = avformat_alloc_context();
    if (!m_Ic) {
        LOGE("Could not allocate context.");
        ret = AVERROR(ENOMEM);
        goto fail;
    }
    m_Ic->interrupt_callback.callback = m_InterrputCB;
    m_Ic->interrupt_callback.opaque = m_CBPara;
    err = avformat_open_input(&m_Ic, m_Url.c_str(), NULL, NULL);
    if (err < 0) {
        ffmpeg_show_error(m_Url.c_str(), err);
        ret = OPEN_INPUT_ERROR;
        goto fail;
    }

    err = avformat_find_stream_info(m_Ic, NULL);
    if (err < 0) {
    	LOGE("%s: could not find codec parameters", m_Url.c_str());
        ffmpeg_show_error(m_Url.c_str(), err);
        ret = FIND_STREAM_INFO_ERROR;
        goto fail;
    }

	m_AudioStream = av_find_best_stream(m_Ic, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
	m_VideoStream = av_find_best_stream(m_Ic, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);

    if(m_Ic->duration != AV_NOPTS_VALUE)
        m_Duration = m_Ic->duration / AV_TIME_BASE;

    if(m_Ic->bit_rate)
        m_StreamBitRate = m_Ic->bit_rate / 1000;
	LOGI("audioStream:%d videoStream:%d", m_AudioStream, m_VideoStream);

    if(m_AudioStream >= 0) {
        AVStream *ast = m_Ic->streams[m_AudioStream];
        AVCodecParameters *apar = m_Ic->streams[m_AudioStream]->codecpar;
        //AVCodecID
        m_AudioCodec = apar->codec_id;
        m_AudioSampleRate = apar->sample_rate;
        m_AudioChannelLayout = apar->channel_layout;
        m_AudioChannels = apar->channels;;
        //AVSampleFormat
        m_AudioFormat = apar->format;
        m_AudioFrameSize = apar->frame_size;
        m_AudioBitRate = apar->bit_rate;
        m_AudioTimeBase = ast->time_base;
        LOGD("m_AudioTimeBase num:%d den:%d", m_AudioTimeBase.num, m_AudioTimeBase.den);
    }

    if(m_VideoStream >= 0) {
        AVStream *vst = m_Ic->streams[m_VideoStream];
        AVCodecParameters *vpar = m_Ic->streams[m_VideoStream]->codecpar;

        m_VideoFPS = av_q2d(vst->avg_frame_rate);
        // AVCodecID
        m_VideoCodec = vpar->codec_id;
        // AVPixelFormat
        m_VideoFormat = vpar->format;
        m_VideoHeight = vpar->height;
        m_VideoWidth = vpar->width;
        m_VideoBitRate = vpar->bit_rate;
        m_VideoTimeBase = vst->time_base;
        LOGD("m_VideoTimeBase num:%d den:%d", m_VideoTimeBase.num, m_VideoTimeBase.den);
    }

	return 0;

fail:
    if (m_Ic)
        avformat_close_input(&m_Ic);
	return ret;
}

void Demuxer::registerInterruptCB(interrput_callback cb, void *lpara)
{
	m_InterrputCB = cb;
	m_CBPara = lpara;
}

bool Demuxer::isVideoPacket(AVPacket &pkt)
{
	return pkt.stream_index == m_VideoStream;
}

bool Demuxer::isAudioPacket(AVPacket &pkt)
{
	return pkt.stream_index == m_AudioStream;
}

bool Demuxer::isKeyFrame(AVPacket &pkt)
{
    return pkt.flags == AV_PKT_FLAG_KEY;
}

void Demuxer::dumpFormat()
{
	LOGD("Demuxer dumpFormat");
	av_dump_format(m_Ic, 0, m_Url.c_str(), 0);
}

int Demuxer::readPacket(AVPacket &pkt)
{
	return av_read_frame(m_Ic, &pkt);
}

bool Demuxer::seek(int second)
{
    int64_t pos = m_VideoTimeBase.den * second;
    LOGI("Demuxer seek second:%d pos:%ld", second, pos);
    int ret = av_seek_frame(m_Ic, m_VideoStream, pos, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME);
    if (ret < 0)  {
        LOGE("av_seek_frame failed, %s", strerror(AVUNERROR(ret)));
    }
    
    return ret == 0 ? true : false;
}

bool Demuxer::isRealtime()
{
    AVFormatContext *s = m_Ic;
    if(!s) {
        LOGE("AVFormatContext is null!");
        return false;
    }

    if(   !strcmp(s->iformat->name, "rtp")
       || !strcmp(s->iformat->name, "rtsp")
       || !strcmp(s->iformat->name, "sdp")
    )
        return true;

    if(s->pb && (   !strncmp(s->url, "rtp:", 4)
                 || !strncmp(s->url, "udp:", 4)
                )
    )
        return true;
    return false;
}